########## Supplementary materials 2 #############

## This code replicates all results from section 2 of the supplementary materials
## This includes:
## 1. Tests of the political determinants of coverage
## 2. Tables for the main DiD estimates presented in the main text
## 3. Models using additional outcomes/elections

## Packages etc

rm(list=ls())

library(fixest)
library(dplyr)
library(ggplot2)

#### FIGURE 5 (balance tests with and without matching weights) ########

#### Load covariates
weights_std <- readRDS("weights_std.rds")

#### Run regression with and without weights for each characteristic

bal_test1 <- feols(c(
  elevation, 
  distance_road_2016,
  streams_raw,
  dep_ratio, 
  poverty11,
  nightlights_2014,
  pop_density14) ~ treated,
  data=weights_std)

bal_test2 <- feols(c(elevation, 
                     distance_road_2016,
                     streams_raw,
                     dep_ratio, 
                     poverty11,
                     nightlights_2014,
                     pop_density14) ~ treated,
                   data=weights_std, 
                   weights=~weights)

### Save results as data.frame

balance_list <- c(bal_test1, bal_test2)

balance_df <- map_df(balance_list, broom::tidy, .id="model") %>%
  filter(term != "(Intercept)") %>%
  mutate(conf.low = estimate - 1.96*std.error,
         conf.high = estimate + 1.96*std.error,
         outcome = rep(c("Elevation", 
                         "Distance to main road",
                         "Polling streams (2014)",
                         "Dependency ratio", 
                         "Poverty",
                         "Nightlights", 
                         "Population density"), 
                       2), 
         specification = rep(c("Raw", "Matching weights"), each=7)) %>%
  rename(Outcome = outcome, 
         Estimate = estimate, 
         'Standard error' = std.error)

## Make plot

ggplot(data=balance_df, aes(x = Estimate, 
                                       y = 
                                         factor(Outcome,
                                                levels = c(
                                                  "Poverty",
                                                  "Dependency ratio",
                                                  "Distance to main road",
                                                  "Elevation",
                                                  "Polling streams (2014)",
                                                  "Population density",
                                                  "Nightlights")),
                                       group=specification)) +
  facet_wrap(~factor(specification,
                     levels = c("Raw", "Matching weights")), scales="fixed") +
  theme(panel.border = element_rect(colour = "black", fill = NA)) +
  geom_vline(xintercept=0, linetype=2) +
  geom_errorbarh(aes(xmin=conf.low, xmax=conf.high, height=0, width=0, 
                     col=specification),
                 position = position_dodge(width=0.3)) +
  geom_point(position = position_dodge(width=0.3), 
             alpha=0.7, aes(col=specification, shape = specification)) +
  theme_bw() +
  theme(legend.position = "bottom", 
        legend.title = element_blank(),
        axis.title.y = element_blank()) +
  xlab("Difference (standard deviations)") +
  ggtitle("Covariate balance (2014 characteristics, not-yet treated vs control units)") +
  scale_colour_manual(values = c("darkorange", "darkblue"))


#### FIGURE 6 (distribution of polling streams) #########

## Load data
did_election_data <- readRDS("did_election_data_full.rds")

## Separate by race
did_election_data_pres <- did_election_data %>% filter(race == "President") 

ggplot(did_election_data_pres, aes(x=streams_raw)) +
  facet_wrap(~election) +
  geom_bar(col="black", fill="darkgrey") +
  theme_bw() +
  ylab("Count") + xlab("Number of polling streams")


##### Political targeting of coverage #########

## Main dataset
did_election_data_full <- readRDS("did_election_data_full.rds")

## Separate by race
did_election_data_pres_full <- did_election_data_full %>% filter(race == "President") 
did_election_data_parl_full <- did_election_data_full %>% filter(race == "Parliament") 
did_election_data_council_full <- did_election_data_full %>% filter(race == "Council") 
did_election_data_presparl_full <- did_election_data_full %>% filter(race != "Council")


##### Restrict to 2014 outcomes, using 2019 measure of treatment

full_panel_moves_pres2014 <- did_election_data_pres_full %>% filter(election == "2014")
full_panel_moves_parl2014 <- did_election_data_parl_full %>% filter(election == "2014")
full_panel_moves_council2014 <- did_election_data_council_full %>% filter(election == "2014")

#### Run regressions

### With ward FEs

dpp_bal1.1 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_pres2014)

dpp_bal1.2 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_pres2014, 
                    weights = ~weights)

dpp_bal1.3 <- feols(treated ~ rejected_share | ward, 
                    data = full_panel_moves_pres2014)

dpp_bal1.4 <- feols(treated ~ rejected_share | ward, 
                    data = full_panel_moves_pres2014, 
                    weights = ~weights)


dpp_bal2.1 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_parl2014)

dpp_bal2.2 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_parl2014, 
                    weights = ~weights)

dpp_bal3.1 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_council2014)

dpp_bal3.2 <- feols(treated ~ dpp_share | ward, 
                    data = full_panel_moves_council2014, 
                    weights = ~weights)

### With constituency FEs

dpp_bal5.1 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_pres2014)

dpp_bal5.2 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_pres2014, 
                    weights = ~weights)

dpp_bal5.3 <- feols(treated ~ rejected_share | constituency, 
                    data = full_panel_moves_pres2014)

dpp_bal5.4 <- feols(treated ~ rejected_share | constituency, 
                    data = full_panel_moves_pres2014, 
                    weights = ~weights)

dpp_bal6.1 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_parl2014)

dpp_bal6.2 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_parl2014, 
                    weights = ~weights)

dpp_bal7.1 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_council2014)

dpp_bal7.2 <- feols(treated ~ dpp_share | constituency, 
                    data = full_panel_moves_council2014, 
                    weights = ~weights)


### Make tables

setFixest_dict(dpp_share = "Ruling party vote share (%)", 
               rejected_share = "Ballot rejection rate (%)",
               total_cast = "Total votes cast (count)",
               treated = "Pr(Enter coverage before next election)",
               ward = "Ward", 
               constituency = "Constituency")

etable(dpp_bal1.1, dpp_bal1.2,
       dpp_bal5.1, dpp_bal5.2,
       dpp_bal1.3, dpp_bal1.4,
       dpp_bal5.3, dpp_bal5.4,
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes",
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Determinants of future coverage (Presidential election)")

etable(dpp_bal2.1, dpp_bal2.2,
       dpp_bal6.1, dpp_bal6.2,
       dpp_bal3.1, dpp_bal3.2,
       dpp_bal7.1, dpp_bal7.2,
       headers = list("^:_:Election" = list("Parliamentary" = 4, "Council" = 4)),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes",
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Determinants of future coverage (Other elections)")


####### Main results (tables 15-18) ############

## Load dataset
rm(list=ls())
did_election_data <- readRDS("did_election_data_full.rds") %>%
  filter(group %in% c("Control", "Mid")) ## Filter as discussed in main text

did_election_data <- did_election_data %>%
  filter(!is.na(dpp_share))

## Separate by race
did_election_data_pres <- did_election_data %>% filter(race == "President") 
did_election_data_parl <- did_election_data %>% filter(race == "Parliament") 
did_election_data_council <- did_election_data %>% filter(race == "Council") 
did_election_data_presparl <- did_election_data %>% filter(race != "Council")

#### Set up function to run DiD specification

did_model <- function(outcome, fixed_effect, include_streams = FALSE, include_weights = FALSE) {
  
  # Define the base formula
  formula <- as.formula(
    paste0(outcome, " ~ inside", if (include_streams) " + i(streams_raw)" else "", " | ", fixed_effect)
  )
  
  # Run the model with specified arguments
  model <- feols(
    formula,
    data = did_election_data_pres,
    cluster = ~ps_id,
    weights = if (include_weights) ~weights else NULL
  )
  
  return(model)
}

### Run models and store in a list

models_main <- list(
  
  ### DPP VOTE SHARE
  
  ## Baseline
  dpp1.1pres = did_model("dpp_share", "ps_id + election"),
  dpp1.2pres = did_model("dpp_share", "ward + election"),
  dpp1.3pres = did_model("dpp_share", "constituency + election"),
  
  ## Matching
  
  dpp2.1pres = did_model("dpp_share", "ps_id + election", include_weights = TRUE),
  dpp2.2pres = did_model("dpp_share", "ward + election", include_weights = TRUE),
  dpp2.3pres = did_model("dpp_share", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  dpp3.1pres = did_model("dpp_share", "ps_id + election", include_streams = TRUE),
  dpp3.2pres = did_model("dpp_share", "ward + election", include_streams = TRUE),
  dpp3.3pres = did_model("dpp_share", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  dpp4.1pres = did_model("dpp_share", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  dpp4.2pres = did_model("dpp_share", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  dpp4.3pres = did_model("dpp_share", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE),
  
  ### BALLOT REJECTION RATE
  
  ## Baseline
  rej1.1pres = did_model("rejected_share", "ps_id + election"),
  rej1.2pres = did_model("rejected_share", "ward + election"),
  rej1.3pres = did_model("rejected_share", "constituency + election"),
  
  ## Matching
  
  rej2.1pres = did_model("rejected_share", "ps_id + election", include_weights = TRUE),
  rej2.2pres = did_model("rejected_share", "ward + election", include_weights = TRUE),
  rej2.3pres = did_model("rejected_share", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  rej3.1pres = did_model("rejected_share", "ps_id + election", include_streams = TRUE),
  rej3.2pres = did_model("rejected_share", "ward + election", include_streams = TRUE),
  rej3.3pres = did_model("rejected_share", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  rej4.1pres = did_model("rejected_share", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  rej4.2pres = did_model("rejected_share", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  rej4.3pres = did_model("rejected_share", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE)
  
)

### Set dictionary and print tables 

setFixest_dict(dpp_share = "Ruling party vote share (%)", 
               rejected_share = "Ballot rejection rate (%)",
               total_cast = "Total votes cast (count)",
               total_votes_ps = "Total votes cast (count)",
               ps_id = "Polling station", 
               election = "Election", 
               ward = "Ward", 
               constituency = "Constituency", 
               inside = "Enter coverage")


etable(models_main[1], models_main[4],
       models_main[2], models_main[5],
       models_main[3], models_main[6],
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, DPP share")


etable(models_main[7], models_main[10],
       models_main[8], models_main[11],
       models_main[9], models_main[12],
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, DPP share (number of streams)")

etable(models_main[13], models_main[16],
       models_main[14], models_main[17],
       models_main[15], models_main[18], 
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, ballot rejection")


etable(models_main[19], models_main[22],
       models_main[20], models_main[23],
       models_main[21], models_main[24], 
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, ballot rejection (number of streams)")


#### Alternative outcomes (tables 19-24) ######

### Run models with total votes cast, registered voters, and turnout

models_alt_outcomes <- list(
  
  ### TOTAL VOTES CAST
  
  ## Baseline
  total1.1pres = did_model("total_votes_ps", "ps_id + election"),
  total1.2pres = did_model("total_votes_ps", "ward + election"),
  total1.3pres = did_model("total_votes_ps", "constituency + election"),
  
  ## Matching
  
  total2.1pres = did_model("total_votes_ps", "ps_id + election", include_weights = TRUE),
  total2.2pres = did_model("total_votes_ps", "ward + election", include_weights = TRUE),
  total2.3pres = did_model("total_votes_ps", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  total3.1pres = did_model("total_votes_ps", "ps_id + election", include_streams = TRUE),
  total3.2pres = did_model("total_votes_ps", "ward + election", include_streams = TRUE),
  total3.3pres = did_model("total_votes_ps", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  total4.1pres = did_model("total_votes_ps", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  total4.2pres = did_model("total_votes_ps", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  total4.3pres = did_model("total_votes_ps", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE),
  
  ### REGISTERED VOTERS
  
  ## Baseline
  reg1.1pres = did_model("registered", "ps_id + election"),
  reg1.2pres = did_model("registered", "ward + election"),
  reg1.3pres = did_model("registered", "constituency + election"),
  
  ## Matching
  
  reg2.1pres = did_model("registered", "ps_id + election", include_weights = TRUE),
  reg2.2pres = did_model("registered", "ward + election", include_weights = TRUE),
  reg2.3pres = did_model("registered", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  reg3.1pres = did_model("registered", "ps_id + election", include_streams = TRUE),
  reg3.2pres = did_model("registered", "ward + election", include_streams = TRUE),
  reg3.3pres = did_model("registered", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  reg4.1pres = did_model("registered", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  reg4.2pres = did_model("registered", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  reg4.3pres = did_model("registered", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE),
  
  ### TURNOUT
  
  ## Baseline
  turnout1.1pres = did_model("turnout", "ps_id + election"),
  turnout1.2pres = did_model("turnout", "ward + election"),
  turnout1.3pres = did_model("turnout", "constituency + election"),
  
  ## Matching
  
  turnout2.1pres = did_model("turnout", "ps_id + election", include_weights = TRUE),
  turnout2.2pres = did_model("turnout", "ward + election", include_weights = TRUE),
  turnout2.3pres = did_model("turnout", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  turnout3.1pres = did_model("turnout", "ps_id + election", include_streams = TRUE),
  turnout3.2pres = did_model("turnout", "ward + election", include_streams = TRUE),
  turnout3.3pres = did_model("turnout", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  turnout4.1pres = did_model("turnout", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  turnout4.2pres = did_model("turnout", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  turnout4.3pres = did_model("turnout", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE)
  
)

### Set dictionary and print tables 

setFixest_dict(dpp_share = "Ruling party vote share (%)", 
               rejected_share = "Ballot rejection rate (%)",
               total_valid_votes = "Total votes cast (count)",
               race = "Race type",
               registered = "Registered", 
               turnout = "Turnout",
               ps_id = "Polling station", 
               election = "Election", 
               ward = "Ward", 
               constituency = "Constituency", 
               inside = "Enter coverage")

etable(models_alt_outcomes[1], models_alt_outcomes[4], 
       models_alt_outcomes[2], models_alt_outcomes[5],  
       models_alt_outcomes[3], models_alt_outcomes[6], 
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Total votes cast")


etable(models_alt_outcomes[7], models_alt_outcomes[10], 
       models_alt_outcomes[8], models_alt_outcomes[11],  
       models_alt_outcomes[9], models_alt_outcomes[12], 
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Total votes cast (number of streams)")

etable(models_alt_outcomes[13], models_alt_outcomes[16], 
       models_alt_outcomes[14], models_alt_outcomes[17],  
       models_alt_outcomes[15], models_alt_outcomes[18], 
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Registered voters")


etable(models_alt_outcomes[19], models_alt_outcomes[22], 
       models_alt_outcomes[20], models_alt_outcomes[23],  
       models_alt_outcomes[21], models_alt_outcomes[24],
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Registered voters (number of streams)")

etable(models_alt_outcomes[25], models_alt_outcomes[28], 
       models_alt_outcomes[26], models_alt_outcomes[29],  
       models_alt_outcomes[27], models_alt_outcomes[30],
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Turnout")


etable(models_alt_outcomes[31], models_alt_outcomes[34], 
       models_alt_outcomes[32], models_alt_outcomes[35],  
       models_alt_outcomes[33], models_alt_outcomes[36],
       order = c("Earlier roll-out"),
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results: Turnout (number of streams)")


#### Interpolated coverage data ######

### Run models using interpolated coverage status for stations with missing data

## Load updated datasets for each % cut-off
## e.g. "60" suggests all stations are treated if >= 60% of ward is in coverage
## and so on up to >= 90%

rm(list=ls())
load("ward_coverage.RData")

##### Update function to take multiple datasets

# Define the function to estimate models across multiple datasets
did_model2 <- function(outcome, fixed_effect, datasets, include_streams = FALSE) {
  
  # Create an empty list to store models
  models <- list()
  
  # Loop over each dataset
  for (data_name in names(datasets)) {
    # Define the formula with or without streams
    formula <- as.formula(
      paste0(outcome, " ~ inside", if (include_streams) " + i(streams_raw)" else "", " | ", fixed_effect)
    )
    
    # Run the model on the current dataset
    model <- feols(
      formula,
      data = datasets[[data_name]],  # Access dataset by name
      cluster = ~ps_id
    )
    
    # Store the model in the list with a descriptive name
    model_name <- paste0(outcome, "_", data_name, if (include_streams) "_streams" else "")
    models[[model_name]] <- model
  }
  
  return(models)
}
#### Re-run main specifications using each alt. dataset

datasets <- list(
  pres_election_panel50 = pres_election_panel50,
  pres_election_panel60 = pres_election_panel60,
  pres_election_panel70 = pres_election_panel70,
  pres_election_panel80 = pres_election_panel80,
  pres_election_panel90 = pres_election_panel90
)

models <- did_model2("dpp_share", "ps_id + election", datasets)

models_ward <- list(
  
  ### DPP VOTE SHARE
  
  ## Baseline
  dpp1.1pres = did_model2("dpp_share", "ps_id + election", datasets),
  dpp1.2pres = did_model2("dpp_share", "ward + election", datasets),
  dpp1.3pres = did_model2("dpp_share", "constituency + election", datasets),
  
  ## Streams
  
  dpp3.1pres = did_model2("dpp_share", "ps_id + election", 
                         datasets, include_streams = TRUE),
  dpp3.2pres = did_model2("dpp_share", "ward + election", 
                         datasets, include_streams = TRUE),
  dpp3.3pres = did_model2("dpp_share", "constituency + election", 
                         datasets, include_streams = TRUE),
  
  ### BALLOT REJECTION RATE
  
  ## Baseline
  rej1.1pres = did_model2("rejected_share", "ps_id + election", datasets),
  rej1.2pres = did_model2("rejected_share", "ward + election", datasets),
  rej1.3pres = did_model2("rejected_share", "constituency + election", datasets),
  
  ## Streams
  
  rej3.1pres = did_model2("rejected_share", "ps_id + election", 
                          datasets, include_streams = TRUE),
  rej3.2pres = did_model2("rejected_share", "ward + election", 
                          datasets, include_streams = TRUE),
  rej3.3pres = did_model2("rejected_share", "constituency + election", 
                          datasets, include_streams = TRUE)
  
)

### Print tables

etable(models_ward[[1]][[1]], models_ward[[4]][[1]], 
       models_ward[[2]][[1]], models_ward[[5]][[1]],  
       models_ward[[3]][[1]], models_ward[[6]][[1]], 
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on incumbent support (50% ward-coverage)")


etable(models_ward[[7]][[1]], models_ward[[10]][[1]], 
       models_ward[[8]][[1]], models_ward[[11]][[1]],  
       models_ward[[9]][[1]], models_ward[[12]][[1]],
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on ballot rejection (50% ward-coverage)")

etable(models_ward[[1]][[2]], models_ward[[4]][[2]], 
       models_ward[[2]][[2]], models_ward[[5]][[2]],  
       models_ward[[3]][[2]], models_ward[[6]][[2]], 
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on incumbent support (60% ward-coverage)")


etable(models_ward[[7]][[2]], models_ward[[10]][[2]], 
       models_ward[[8]][[2]], models_ward[[11]][[2]],  
       models_ward[[9]][[2]], models_ward[[12]][[2]],
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on ballot rejection (60% ward-coverage)")

etable(models_ward[[1]][[3]], models_ward[[4]][[3]], 
       models_ward[[2]][[3]], models_ward[[5]][[3]],  
       models_ward[[3]][[3]], models_ward[[6]][[3]], 
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on incumbent support (70% ward-coverage)")

etable(models_ward[[7]][[3]], models_ward[[10]][[3]], 
       models_ward[[8]][[3]], models_ward[[11]][[3]],  
       models_ward[[9]][[3]], models_ward[[12]][[3]],
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on ballot rejection (70% ward-coverage)")

etable(models_ward[[1]][[4]], models_ward[[4]][[4]], 
       models_ward[[2]][[4]], models_ward[[5]][[4]],  
       models_ward[[3]][[4]], models_ward[[6]][[4]], 
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on incumbent support (80% ward-coverage)")

etable(models_ward[[7]][[4]], models_ward[[10]][[4]], 
       models_ward[[8]][[4]], models_ward[[11]][[4]],  
       models_ward[[9]][[4]], models_ward[[12]][[4]],
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on ballot rejection (80% ward-coverage)")

etable(models_ward[[1]][[5]], models_ward[[4]][[5]], 
       models_ward[[2]][[5]], models_ward[[5]][[5]],  
       models_ward[[3]][[5]], models_ward[[6]][[5]], 
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on incumbent support (90% ward-coverage)")


etable(models_ward[[7]][[5]], models_ward[[10]][[5]], 
       models_ward[[8]][[5]], models_ward[[11]][[5]],  
       models_ward[[9]][[5]], models_ward[[12]][[5]],
       extralines=list("_^Streams control"=c("", "Yes", 
                                             "", "Yes", 
                                             "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results on ballot rejection (90% ward-coverage)")


#### Additional elections data ######

### Run models using data from parliamentary and council elections

#### Updated function for running results

# Define the function to estimate models across multiple datasets
did_model3 <- function(outcome, fixed_effect, datasets, include_streams = FALSE, include_weights = FALSE) {
  
  # Create an empty list to store models
  models <- list()
  
  # Loop over each dataset
  for (data_name in names(datasets)) {
    # Define the formula with or without streams
    formula <- as.formula(
      paste0(outcome, " ~ inside", if (include_streams) " + i(streams_raw)" else "", " | ", fixed_effect)
    )
    
    # Run the model on the current dataset
    model <- feols(
      formula,
      data = datasets[[data_name]],  # Access dataset by name
      cluster = ~ps_id,
      weights = if (include_weights) ~weights else NULL
    )
    
    # Store the model in the list with a descriptive name
    model_name <- paste0(outcome, "_", data_name, if (include_streams) "_streams" else "", if (include_weights) "_weighted" else "")
    models[[model_name]] <- model
  }
  
  return(models)
}

### Running results 

datasets <- list(
  did_election_data = did_election_data,
  did_election_data_parl = did_election_data_parl, 
  did_election_data_council = did_election_data_council, 
  did_election_data_presparl = did_election_data_presparl
)


models_races <- list(
  
  ### DPP VOTE SHARE
  
  ## Baseline
  dpp1.1 = did_model3("dpp_share", "ps_id + race + election", datasets),
  dpp1.2 = did_model3("dpp_share", "ward + race + election", datasets),
  dpp1.3 = did_model3("dpp_share", "constituency + race + election", datasets),
  
  ## Matching
  dpp2.1 = did_model3("dpp_share", "ps_id + race + election", datasets, include_weights = TRUE),
  dpp2.2 = did_model3("dpp_share", "ward + race + election", datasets, include_weights = TRUE),
  dpp2.3 = did_model3("dpp_share", "constituency + race + election", datasets, include_weights = TRUE),
  
  ## Baseline with streams
  dpp3.1 = did_model3("dpp_share", "ps_id + race + election", datasets, include_streams = TRUE),
  dpp3.2 = did_model3("dpp_share", "ward + race + election", datasets, include_streams = TRUE),
  dpp3.3 = did_model3("dpp_share", "constituency + race + election", datasets, include_streams = TRUE),
  
  ## Matching with streams
  dpp4.1 = did_model3("dpp_share", "ps_id + race + election", datasets, 
                      include_streams = TRUE, include_weights = TRUE),
  dpp4.2 = did_model3("dpp_share", "ward + race + election", datasets, 
                      include_streams = TRUE, include_weights = TRUE),
  dpp4.3 = did_model3("dpp_share", "constituency + race + election", datasets, 
                      include_streams = TRUE, include_weights = TRUE)
  
  )

### Print tables

etable(models_races[[1]][[1]], models_races[[4]][[1]], 
       models_races[[2]][[1]], models_races[[5]][[1]],  
       models_races[[3]][[1]], models_races[[6]][[1]], 
       extralines=list("_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results (All races combined)")

etable(models_races[[7]][[1]], models_races[[10]][[1]], 
       models_races[[8]][[1]], models_races[[11]][[1]],  
       models_races[[9]][[1]], models_races[[12]][[1]], 
       extralines=list("_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes"),
                       "_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results with number of streams (All races combined)")

etable(models_races[[1]][[2]], models_races[[4]][[2]], 
       models_races[[2]][[2]], models_races[[5]][[2]],  
       models_races[[3]][[2]], models_races[[6]][[2]], 
       extralines=list("_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results (Parliament)")

etable(models_races[[7]][[2]], models_races[[10]][[2]], 
       models_races[[8]][[2]], models_races[[11]][[2]],  
       models_races[[9]][[2]], models_races[[12]][[2]], 
       extralines=list("_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes"),
                       "_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results with number of streams (Parliament)")

etable(models_races[[1]][[3]], models_races[[4]][[3]], 
       models_races[[2]][[3]], models_races[[5]][[3]],  
       models_races[[3]][[3]], models_races[[6]][[3]], 
       extralines=list("_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results (Council)")

etable(models_races[[7]][[3]], models_races[[10]][[3]], 
       models_races[[8]][[3]], models_races[[11]][[3]],  
       models_races[[9]][[3]], models_races[[12]][[3]], 
       extralines=list("_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes"),
                       "_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results with number of streams (Council)")

etable(models_races[[1]][[4]], models_races[[4]][[4]], 
       models_races[[2]][[4]], models_races[[5]][[4]],  
       models_races[[3]][[4]], models_races[[6]][[4]], 
       extralines=list("_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results (President and Parliament)")

etable(models_races[[7]][[4]], models_races[[10]][[4]], 
       models_races[[8]][[4]], models_races[[11]][[4]],  
       models_races[[9]][[4]], models_races[[12]][[4]], 
       extralines=list("_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes"),
                       "_^Weights"=c("", "Yes", 
                                     "", "Yes", 
                                     "", "Yes")),
       drop = c("streams_raw"),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results with number of streams (President and Parliament)")


####### OCI coverage #########

### Load in pres election data with OCI coverage measure

oci_data <- readRDS("did_election_data_full.rds") %>%
  filter(group %in% c("Control", "Late")) %>%
  filter(race == "President")

### Re-run core regressions

did_model4 <- function(outcome, fixed_effect, include_streams = FALSE, include_weights = FALSE) {
  
  # Define the base formula
  formula <- as.formula(
    paste0(outcome, " ~ inside", if (include_streams) " + i(streams_raw)" else "", " | ", fixed_effect)
  )
  
  # Run the model with specified arguments
  model <- feols(
    formula,
    data = oci_data,
    cluster = ~ps_id,
    weights = if (include_weights) ~weights else NULL
  )
  
  return(model)
}

models_oci <- list(
  
  ### DPP VOTE SHARE
  
  ## Baseline
  dpp1.1pres = did_model4("dpp_share", "ps_id + election"),
  dpp1.2pres = did_model4("dpp_share", "ward + election"),
  dpp1.3pres = did_model4("dpp_share", "constituency + election"),
  
  ## Matching
  
  dpp2.1pres = did_model4("dpp_share", "ps_id + election", include_weights = TRUE),
  dpp2.2pres = did_model4("dpp_share", "ward + election", include_weights = TRUE),
  dpp2.3pres = did_model4("dpp_share", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  dpp3.1pres = did_model4("dpp_share", "ps_id + election", include_streams = TRUE),
  dpp3.2pres = did_model4("dpp_share", "ward + election", include_streams = TRUE),
  dpp3.3pres = did_model4("dpp_share", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  dpp4.1pres = did_model4("dpp_share", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  dpp4.2pres = did_model4("dpp_share", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  dpp4.3pres = did_model4("dpp_share", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE),
  
  ### BALLOT REJECTION RATE
  
  ## Baseline
  rej1.1pres = did_model4("rejected_share", "ps_id + election"),
  rej1.2pres = did_model4("rejected_share", "ward + election"),
  rej1.3pres = did_model4("rejected_share", "constituency + election"),
  
  ## Matching
  
  rej2.1pres = did_model4("rejected_share", "ps_id + election", include_weights = TRUE),
  rej2.2pres = did_model4("rejected_share", "ward + election", include_weights = TRUE),
  rej2.3pres = did_model4("rejected_share", "constituency + election", include_weights = TRUE),
  
  ## Baseline with streams
  
  rej3.1pres = did_model4("rejected_share", "ps_id + election", include_streams = TRUE),
  rej3.2pres = did_model4("rejected_share", "ward + election", include_streams = TRUE),
  rej3.3pres = did_model4("rejected_share", "constituency + election", include_streams = TRUE),
  
  ## Matching with streams
  rej4.1pres = did_model4("rejected_share", "ps_id + election", 
                         include_streams = TRUE, include_weights = TRUE),
  rej4.2pres = did_model4("rejected_share", "ward + election", 
                         include_streams = TRUE, include_weights = TRUE),
  rej4.3pres = did_model4("rejected_share", "constituency + election", 
                         include_streams = TRUE, include_weights = TRUE)
  
)

### Set dictionary and print tables 

setFixest_dict(dpp_share = "Ruling party vote share (%)", 
               rejected_share = "Ballot rejection rate (%)",
               total_cast = "Total votes cast (count)",
               total_votes_ps = "Total votes cast (count)",
               ps_id = "Polling station", 
               election = "Election", 
               ward = "Ward", 
               constituency = "Constituency", 
               inside = "Enter coverage (OCI)")


etable(models_oci[1], models_oci[4],
       models_oci[2], models_oci[5],
       models_oci[3], models_oci[6],
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, DPP share")


etable(models_oci[7], models_oci[10],
       models_oci[8], models_oci[11],
       models_oci[9], models_oci[12],
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, DPP share (number of streams)")

etable(models_oci[13], models_oci[16],
       models_oci[14], models_oci[17],
       models_oci[15], models_oci[18], 
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, ballot rejection")


etable(models_oci[19], models_oci[22],
       models_oci[20], models_oci[23],
       models_oci[21], models_oci[24], 
       drop = c("streams_raw"),
       extralines=list("_^Weights"=c("", "Yes",
                                     "", "Yes", 
                                     "", "Yes"),
                       "_^Streams control"=c("Yes", "Yes", 
                                             "Yes", "Yes", 
                                             "Yes", "Yes")),
       tex=TRUE, 
       fontsize = "scriptsize", 
       title = "Difference-in-differences results, ballot rejection (number of streams)")













